/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package genomemap.data;

import commons.util.FreqDist;
import genomemap.map.MinimalContigMap;
import genomemap.map.ProbeMap;

import java.util.*;

/**
 *
 *
 * @version 1.3.0
 * @author Susanta Tewari
 * @history Created on 2/9/13
 * @since 1.3.0
 */
public class OrganismDataUtil {

    private OrganismDataUtil() {}

    /**
     * @return total number of hybridizations present in {@code data}
     */
    public static int countSignal(final OrganismData data) {

        int result = 0;

        for (final int linkageGroup : data.getLinkageGroups()) {

            final KSA00Data ksa00Data = data.createKSA00Data(linkageGroup,
                                            data.probes(linkageGroup));

            result += ksa00Data.countHybridization();
        }

        return result;
    }

    /**
     * @return total number of probes present in {@code data}
     */
    public static int countProbes(final OrganismData data) {

        int result = 0;

        for (final int linkageGroup : data.getLinkageGroups()) {
            result += data.probes(linkageGroup).size();
        }

        return result;
    }

    /**
     * @return total length of chromosomes present in {@code data}
     */
    public static int genomeLength(final OrganismData data) {

        int result = 0;

        for (final int linkageGroup : data.getLinkageGroups()) {

            final KSA00Data ksa00Data = data.createKSA00Data(linkageGroup,
                                            data.probes(linkageGroup));

            result += ksa00Data.getModel().getChromosomeLength();
        }

        return result;
    }

    /**
     * @param data instance of OrganismData
     * @return clone repeats count for all linkage groups
     */
    public static Map<String, Integer> repeats(final OrganismData data) {

        final FreqDist<String> freqDist = new FreqDist<>();

        for (final Integer linkageGroup : data.getLinkageGroups()) {
            freqDist.add(data.clones(linkageGroup));
        }

        return freqDist.getFrequencyDist();
    }

    /**
     * Transition counts between states S (1), R(>1) and A(all).
     *
     * @param data organism data
     * @return key: linkage group; value: transition counts
     */
    public static Map<Integer, int[][]> countRepeatsTransitions(final OrganismData data) {

        final Map<String, Integer> cloneRepeats = repeats(data);
        final List<Integer> linkageGroups       = new ArrayList<>(data.getLinkageGroups());
        final int lgCount                       = linkageGroups.size();
        final Map<Integer, int[][]> result      = new HashMap<>(lgCount);

        for (final Integer lg : linkageGroups) {

            final List<String> probes  = data.probes(lg);
            final ProbeMap probeMap    = new ProbeMap(data.createKSA00Data(lg, probes), "");
            final MinimalContigMap map = new MinimalContigMap(probeMap);

            result.put(lg, countRepeatsTransitions(map, cloneRepeats, lgCount));
        }

        return result;
    }

    /**
     * Counts of the repeat states S (1), R(>1) and A(all).
     *
     * @param data organism data
     * @return key: linkage group; value: counts
     */
    public static Map<Integer, int[]> countRepeats(final OrganismData data) {

        final Map<String, Integer> cloneRepeats = repeats(data);
        final List<Integer> linkageGroups       = new ArrayList<>(data.getLinkageGroups());
        final int lgCount                       = linkageGroups.size();
        final Map<Integer, int[]> result        = new HashMap<>(linkageGroups.size());

        for (final Integer lg : linkageGroups) {

            final List<String> probes  = data.probes(lg);
            final ProbeMap probeMap    = new ProbeMap(data.createKSA00Data(lg, probes), "");
            final MinimalContigMap map = new MinimalContigMap(probeMap);

            result.put(lg, countRepeats(map, cloneRepeats, lgCount));
        }

        return result;
    }

    /**
     * @param map minimal contig map (probes with connected clones, if any) for a chromosome
     * @param cloneRepeats keys as clones with their repeat count as values
     * @param lgCount total number of linkage groupsbeing considered; note that this is important
     * to the definition of repeat state A (all). Note that it must be consistent with cloneRepeats
     * in that they both must come from the equivalent OrganismData instance.
     * @return counts among states S, R & A
     */
    private static int[] countRepeats(final MinimalContigMap map,
                                      final Map<String, Integer> cloneRepeats, final int lgCount) {

        final List<String> clones = map.getClones();
        final int[] result        = new int[3];

        for (int i = 1; i < clones.size(); i++) {

            final String clone   = clones.get(i);
            final Integer repeat = cloneRepeats.get(clone);

            if (isSClone(repeat)) result[0]++;
            if (isRClone(repeat, lgCount)) result[0]++;
            if (isAClone(repeat, lgCount)) result[0]++;
        }

        return result;
    }

    /**
     * @param map minimal contig map (probes with connected clones, if any) for a chromosome
     * @param cloneRepeats keys as clones with their repeat count as values
     * @param lgCount total number of linkage groupsbeing considered; note that this is important
     * to the definition of repeat state A (all). Note that it must be consistent with cloneRepeats
     * in that they both must come from the equivalent OrganismData instance.
     * @return transition counts among states S, R & A
     */
    private static int[][] countRepeatsTransitions(final MinimalContigMap map,
            final Map<String, Integer> cloneRepeats, final int lgCount) {

        final List<String> clones = map.getClones();
        final int[][] result      = new int[3][3];

        for (int i = 1; i < clones.size(); i++) {

            final String clone1    = clones.get(i - 1);
            final String clone2    = clones.get(i);
            final Integer repeat_1 = cloneRepeats.get(clone1);
            final Integer repeat_2 = cloneRepeats.get(clone2);

            if (isSClone(repeat_1) && isSClone(repeat_2)) result[0][0]++;
            if (isSClone(repeat_1) && isRClone(repeat_2, lgCount)) result[0][1]++;
            if (isSClone(repeat_1) && isAClone(repeat_2, lgCount)) result[0][2]++;
            if (isRClone(repeat_1, lgCount) && isSClone(repeat_2)) result[1][0]++;
            if (isRClone(repeat_1, lgCount) && isRClone(repeat_2, lgCount)) result[1][1]++;
            if (isRClone(repeat_1, lgCount) && isAClone(repeat_2, lgCount)) result[1][2]++;
            if (isAClone(repeat_1, lgCount) && isSClone(repeat_2)) result[2][0]++;
            if (isAClone(repeat_1, lgCount) && isRClone(repeat_2, lgCount)) result[2][1]++;
            if (isAClone(repeat_1, lgCount) && isAClone(repeat_2, lgCount)) result[2][2]++;
        }

        return result;
    }

    /**
     * @param repeat repeat count
     * @param max maximum repeat count
     * @return {@code true} if the repeat count equals the max
     */
    private static boolean isAClone(final Integer repeat, final int max) {
        return repeat == max;
    }

    /**
     * @param repeat repeat count
     * @param max maximum repeat count
     * @return {@code true} if the repeat count is greater than 1 but less than max
     */
    private static boolean isRClone(final Integer repeat, final int max) {
        return (repeat > 1) && (repeat < max);
    }

    /**
     * @param repeat repeat count
     * @return {@code true} if the repeat count equals 1
     */
    private static boolean isSClone(final int repeat) {
        return repeat == 1;
    }

    /**
     * @param data instance of OrganismData
     * @return probe maps for all linkage groups
     */
    public static List<ProbeMap> probeMaps(final OrganismData data) {

        final Set<Integer> linkageGroups = data.getLinkageGroups();
        final List<ProbeMap> probeMaps   = new ArrayList<>(linkageGroups.size());

        for (final Integer linkageGroup : linkageGroups) {

            final KSA00Data ksa00Data = data.createKSA00Data(linkageGroup, null);

            probeMaps.add(new ProbeMap(ksa00Data, "probemap-" + linkageGroup));
        }

        return probeMaps;
    }
}
